#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "recorrer.h"
#include "TDAClienteCorreo.h"
#include "TDACorreo.h"
#include "TDACanalSeguro.h"
#include "TDAServidorInseguro.h"
#include "logdebug.h"
#include "cola.h"
#include "listasimple.h"
#include "vignere.h"

/*El archivo estandar para cuando no se especifican mensajes a enviar por linea de comandos*/
#define ARCHIVO_ESTANDAR "mensaje.txt"

/*COMIENZO DE FUNCIONES AUXILIARES*/

/*
 * Genera una clave aleatoria para usar en la encriptacion
 *
 * Parametros:
 * clave	un char* que apunta a un buffer donde se copiara la clave generada
 *
 * Valor de retorno:
 * N/A
 *
 * PRE: clave apunta a una zona de memoria valida con espacio suficiente para contener MAX_KEY_LEN caracteres y ademas el caracter terminador de cadena
 * POST: se ha generado clave para encriptacion y se la ha copiado en clave
 *
 */
void generarClave(char* clave) {
    int keylen,i;
    srand(time(NULL));

    /*Genero el largo de la clave*/
    keylen = rand() % MAX_KEY_LEN + 1;

    /*Genero los valores de la clave*/
    for(i=0;i<keylen;i++)
        clave[i] = rand() % 60 + 64;
    clave[i] = atoi("\0");
}

/*
 * Devuelve 0 para que al comparar siempre de OK (Esto se utiliza en recorrer para que ejecute la accion en
 * todos los elementos de una lista
 * Parametros:
 * a y b	cualquier valor
 *
 * Valor de retorno:
 * Siempre 0
 *
 * PRE: N/A
 * POST: Se devolvió 0 para los elementos comparados
 *
 */
int comparar(void *a, void *b){
    return 0;
}

/*
 * Escribe un correo en un archivo o por pantalla
 *
 * Parametros:
 * elem un puntero a un correo válido
 * arg un puntero a la dirección del destinatario
 *
 * Valor de retorno:
 * N/A
 *
 * PRE: elem apunta a un correo válido y arg apunta a una cadena que contiene el destinatario o a NULL si el
 *      archivo debe ser mostrado por pantalla
 * POST: se ha escrito el correo en un archivo <dir del remitente>_<dir del destinatario> o en pantalla
 *       segun corresponda
 *
 */
void escribir(void *elem, void *arg) {
	/* El archivo a exportar*/
    FILE* archivoOut;

    /*El nombre del archivo a exportar*/
    char recv[MAX_CONTENT_LEN];

    TDACorreo *correo = (TDACorreo*)(elem);
    char *dest = (char*)(arg);

    /*Creo el archivo de salida*/
    if(dest==NULL)
        archivoOut = stdout; /*Si no hay archivos por linea de comandos se envia todo a pantalla*/
    else {
        strcpy(recv,"\0");
        strcat(recv,correo->De);
        strcat(recv,"_");
        strcat(recv,dest);
        archivoOut=fopen(recv,"w");
    }
    if (archivoOut==NULL) {
        logmacro("%s\n", "Error al intentar crear el archivo de salida");
        strcpy(dest,"\0");
        return;
    }

    /*Grabo el correo en el archivo*/
    escribirA(*correo,archivoOut);

    /*Cierro el archivo*/
    fclose(archivoOut);
}


/*FIN DE FUNCIONES AUXILIARES*/

/*
 * Enunciado:
 *
 * Se desea simular un escenario de envio de correos desde un cliente de correo a otro,
 * a traves de un servidor. El servidor utiliza un canal inseguro para comunicarse con
 * los clientes, por lo que se ha decidido utilizar un mecanismo de encriptacion simetrica
 * para proteger el cuerpo de los correos. La clave para la encriptacion se transfiere del
 * cliente de correo emisor al receptor en forma directa por medio de un canal seguro, antes
 * del envio del correo.
 *
 * Como sistema criptografico se sugiere utilizar el cifrado de Vignere, por ser una variante
 * mas elaborada que el Caesar, pero no tan compleja como otros algoritmos existentes.
 *
 * El sistema involucra la lectura y escritura de archivos con los contenidos de los mensajes.
 * A tal fin se utilizara un formato del estilo del estipulado en la RFC 822, que se ejemplifica
 * en los archivos .txt de ejemplo provistos.
 *
 * A partir de los archivos .h provistos, y considerando que la implementacion de TDACanalSeguro
 * es provista en una biblioteca, codificar todos los .c necesarios para implementar los TDA del
 * sistema. Codificar ademas un programa de aplicacion que realice la simulacion, de acuerdo con
 * los lineamientos especificados en la funcion main. Este programa no debe tener interaccion con el
 * usuario: simplemente informara en que paso de la simulacion se encuentra y dejara todas las salidas
 * en los archivos indicados a fines de que quien ejecute el programa de aplicacion pueda leerlos.
 *
 * Aquella informacion relevante que sea necesaria para poder hacer un correcto seguimiento
 * de la simulacion (por ejemplo, la clave utilizada y el contenido del mensaje encriptado)
 * y que no sea provista por la interfaz publica de los TDA, debera indicarse por la salida de
 * error, junto con toda otra informacion de utilidad para el seguimiento de la ejecucion.
 * EXCEPCIONALMENTE, Y SOLO A LOS FINES DE NO AUMENTAR LA COMPLEJDAD EL TRABAJO, SE
 * PERMITIRA INCLUIR LLAMADAS A FUNCIONES DE ENTRADA/SALIDA EN LOS TDA PARA PODER GRABAR
 * LA INFORMACION NECESARIA PARA PERMITIR LA CORRECCION DEL TRABAJO. SIN EMBARGO, ESTAS
 * LLAMADAS DEBERAN REALIZARSE MEDIANTE LA MACRO PROVISTA EN EL ARCHIVO logdebug.h Y NO
 * LLAMANDO A fprint O FUNCIONES SIMILARES EN FORMA DIRECTA. Debera hacerse un include de
 * logdebug.h en cada archivo desde el cual se desee agregar contenido al log. Ver los
 * ejemplos de llamadas en la funcion main.
 */

int main(int argc, char **argv) {

	/* El programa de aplicacion debe leer todos los argumentos indicados por linea de comando.
	 * Cada argumento (excepto el primero, que es el nombre del propio programa) indica un
	 * archivo con un correo a importar y enviar. El programa debe enviar los correos y producir,
	 * por cada uno, un archivo con el mismo nombre que el utilizado para importar pero agregando
	 * el sufijo _recv al nombre.
	 * Si no se indica archivo alguno por linea de comando, el programa debe enviar un correo
	 * predefinido (utilizando los setters de TDACorreo) y mostrar el correo recibido por
	 * salida estandar (utilizando los getters de TDACorreo)
	 */

	/* El presente ejemplo realiza todos los crear al principio y todos los destruir al final
	 * con el proposito de aumentar la claridad para el alumno.
	 * No obstante, debe considerarse que en el programa de aplicacion definitivo las invocaciones
	 * a los destruir podrian realizarse en forma mas temprana, por lo que las primitivas de los
	 * TDA deberian realizar "copias defensivas" (esto es, no guardar en las estructuras los punteros
	 * pasados por parametro, sino utilizar los getters y setters de dichos parametros para realizar
	 * copias propias que no puedan ser destruidas desde afuera) a fines de evitar efectos secundarios.
	 */

	/* Declaro las variables que voy a utilizar */
	/* Una cola y su elemento para contener todos los clientes de correo */
	TCola clientes;
	TDAClienteCorreo cliente;
	/* Una variable para el correo a enviar, una lista para el recibido y su elemento */
	TDACorreo correoEnviado;
	TListaSimple correoRecibido;
	TDACorreo correo;
	/* El canal seguro*/
	/* Nota: La lib canalSeguro para linux falla, se usa un puntero en memoria para simular su actuacion*/
	char canalSeguro[MAX_KEY_LEN];
	/* El servidor Inseguro */
	TDAServidorInseguro servidor;
	/* Los archivos a importar y archivo de log*/
	FILE* archivoIn;
	FILE* archivoLog;
	/* La cantidad de correo enviado */
	int env;
	/*El nombre del destinatario de cada correo*/
	char dest[MAX_CONTENT_LEN];

	logmacro("%s %d %s\n","Comenzando la simulacion, hay", argc, "argumento(s) en la linea de comando");

    /* Creo el canal Seguro e Inseguro */
    /* Nota: La lib canalSeguro para linux falla, se usa un puntero en memoria para simular su actuacion*/
    crearServidor(&servidor);
    strcpy(canalSeguro,"\0");

    logmacro("%s\n", "Enviando clave");

    /*Establezco el contenido del canal Seguro*/
    generarClave(canalSeguro);

    /*Una cola para agregar los distintos clientes de correo que voy creando*/
    C_Crear(&clientes,sizeof(TDAClienteCorreo));

    /*Si ingreso archivos para enviar por linea de comandos*/
    for(env=1;env<argc;env++) {

        /*Abro el archivo de entrada*/
        archivoIn = fopen(argv[env], "r");
        if (archivoIn==NULL) {
            logmacro("%s\n", "Error al intentar abrir o leer el archivo de entrada");
            return EXIT_FAILURE;
        }

        /* Creo un correo a partir de un archivo */
        crearCorreo(&correoEnviado);
        importarDesde(&correoEnviado, archivoIn);
        strcpy(correoEnviado.nombre_del_archivo, argv[env]);

        /*Creo los clientes de correo del Remitente y los conecto, los agrego a la cola de Clientes de Correo*/
        crearClienteCorreo(&cliente,correoEnviado.De);
        conectar(&cliente,&servidor);
        C_Agregar(&clientes,&cliente);

        logmacro("%s\n", "Enviando correo");

        /*Seteo la clave en el canal seguro (puntero en memoria) para el cliente de correo actual*/
        strcpy(cliente.clave,canalSeguro);

        /* Envio el correo*/
        if(enviarCorreo(cliente, correoEnviado) != 0) {
            logmacro("%s\n", "Error enviando correo");
            return EXIT_FAILURE;}
        printf("Enviado correo %s con exito\n\n", argv[env]);
        desconectar(&cliente);
        fclose(archivoIn);
    }

    /*Si no ingreso archivos para enviar por linea de comandos*/
    if(argc==1) {
        /*Abro el archivo de entrada*/
        archivoIn = fopen(ARCHIVO_ESTANDAR, "r");
        if (archivoIn==NULL) {
            logmacro("%s\n", "Error al intentar abrir o leer el archivo ESTANDAR");
            return EXIT_FAILURE;
        }

        /* Creo un correo a partir de un archivo */
        crearCorreo(&correoEnviado);
        importarDesde(&correoEnviado, archivoIn);
        strcpy(correoEnviado.nombre_del_archivo, ARCHIVO_ESTANDAR);

        /*Creo los clientes de correo del Remitente y los conecto, los agrego a la cola de Clientes de Correo*/
        crearClienteCorreo(&cliente,correoEnviado.De);
        conectar(&cliente,&servidor);
        C_Agregar(&clientes,&cliente);

        /*Seteo la clave en el canal seguro (puntero en memoria) para el cliente de correo actual*/
        strcpy(cliente.clave,canalSeguro);

        logmacro("%s\n", "Enviando correo");

        /* Envio el correo*/
        if(enviarCorreo(cliente, correoEnviado) != 0) {
            logmacro("%s\n", "Error enviando correo");
            return EXIT_FAILURE;}
        printf("Enviado correo %s con exito\n\n", ARCHIVO_ESTANDAR);
        fclose(archivoIn);
    }

    /* Creo una lista para contener el correo recibido y su elemento */
    ls_Crear(&correoRecibido,sizeof(TDACorreo));
    crearCorreo(&correo);

    /* Recibo el correo y lo grabo */
    while(C_Vacia(clientes)==FALSE) {
        /*Saco un cliente de la cola*/
        C_Sacar(&clientes,&cliente);

        /*Obtengo la clave desde el canal seguro (puntero en memoria) para el cliente de correo actual*/
        strcpy(cliente.clave,canalSeguro);

        /*Obtengo todos los correos para este cliente de correo en la lista*/
        obtenerTodos(cliente,&correoRecibido);

        /*Establezco el archivo de salida*/
        strcpy(dest,"\0");
        strcpy(dest, cliente.direcUsr);

        if(argc>1)
            recorrer(&correoRecibido,0,0,sizeof(TDACorreo),comparar,escribir,dest);
        else recorrer(&correoRecibido,0,0,sizeof(TDACorreo),comparar,escribir,NULL);
             /*Si no hay archivos por linea de comandos envio todo a pantalla*/
    }

    /*Escribo el log a un archivo*/
    archivoLog = fopen(LOG_FILENAME, "w");
    if (archivoLog==NULL) {
        logmacro("%s\n", "Error al intentar crear un archivo para el log");
        return EXIT_FAILURE;
    }
    escribirLog(&servidor,archivoLog);
    fclose(archivoLog);

    /* Destruyo las variables que use para los correos */
    destruirCorreo(&correoEnviado);
    destruirCorreo(&correo);

    /* Destruyo el canal seguro y el servidor */
    /* Nota: La lib canalSeguro para linux falla, se usa un puntero en memoria para simular su actuacion*/
    destruirServidor(&servidor);
    strcpy(canalSeguro,"\0");

    /* Destruyo la variable que use para el cliente de correo*/
    destruirClienteCorreo(&cliente);

    logmacro("%s\n", "Simulacion finalizada");

    return EXIT_SUCCESS;
}
